package com.sevntu.apvs.dModel;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.sevntu.apvs.duelement.Element;

/**
 * Class validates circuits.
 * 
 * @author <a href="mailto:rd.ryly@gmail.com">Ruslan Diachenko</a>
 */
public class CircuitValidation {

	/** Use for log message generation */
	private static final Logger LOG = Logger.getLogger(CircuitValidation.class.getName());

	/**
	 * Invokes tests for list of circuits.
	 * 
	 * @param circuitList
	 *            List of circuits.
	 * @throws DModelException
	 *             If any test failed.
	 */
	public static void validate(List<Element> elemList, List<CircuitStructure> circuitList) throws DModelException {
		CircuitValidation circuitValidation = new CircuitValidation();
		circuitValidation.checkElemID(elemList);
		circuitValidation.checkContactID(elemList, circuitList);
		circuitValidation.checkCircuit(circuitList);
	}

	/**
	 * Checks elements' IDs.
	 * 
	 * @param elemList
	 *            List of elements.
	 * @throws DModelException
	 *             If ID=0 or more then one element have the similar IDs.
	 */
	public void checkElemID(List<Element> elemList) throws DModelException {
		// Set of elements' ID
		Set<Integer> elemIDSet = new HashSet<Integer>();
		for (Element elem : elemList) {
			elemIDSet.add(elem.id);
		}

		if (elemIDSet.contains(0)) {
			throw new DModelException("Element ID must be greater then 0!!");
		}
		if (elemIDSet.size() != elemList.size()) {
			throw new DModelException("Two or more elements have the similar ID!!");
		}
		LOG.info("Elements IDs validation successfuly passed.");
	}

	/**
	 * Checks contacts' IDs for each element in each circuit.
	 * 
	 * @param elemList
	 *            List of elements.
	 * @param circuitList
	 *            List of circuits.
	 * @throws DModelException
	 *             If there's a wrong contact number or element with specified ID wasn't found in the circuit.
	 */
	public void checkContactID(List<Element> elemList, List<CircuitStructure> circuitList) throws DModelException {
		Map<Integer, Element> elemMap = new HashMap<Integer, Element>();
		for (Element elem : elemList) {
			elemMap.put(elem.id, elem);
		}

		Set<Integer> elemContacts;
		Set<Integer> fictElemContacts = new HashSet<Integer>();
		for (CircuitStructure circuit : circuitList) {
			if (circuit.getElemNumOut() != 0) { // If there's not a fictitious element 
				if (elemMap.containsKey(circuit.getElemNumOut())) { // Check contacts for elementOut
					elemContacts = getElemContacts(elemMap.get(circuit.getElemNumOut()).Ncont);
					LOG.debug("Element '" + elemMap.get(circuit.getElemNumOut()).name + "'. All contacts: "
							+ elemContacts + ". Checking output contacts...");
					if (!elemContacts.contains(circuit.getElemContactOut())) {
						throw new DModelException("Error in circuit #" + circuit.getCircuitId() + ". Element '"
								+ elemMap.get(circuit.getElemNumOut()).name + "' has invalid output contact number!!");
					}
				} else {
					throw new DModelException("Error in circuit #" + circuit.getCircuitId()
							+ ". Invalid elementOut ID!!");
				}
			} else {
				fictElemContacts.add(circuit.getElemContactOut());
			}

			if (circuit.getElemNumIn() != 0) { // If there's not a fictitious element
				if (elemMap.containsKey(circuit.getElemNumIn())) { // Check contacts for elementIn
					elemContacts = getElemContacts(elemMap.get(circuit.getElemNumIn()).Ncont);
					LOG.debug("Element '" + elemMap.get(circuit.getElemNumIn()).name + "'. All contacts: "
							+ elemContacts + ". Checking input contacts...");
					if (!elemContacts.contains(circuit.getElemContactIn())) {
						throw new DModelException("Error in circuit #" + circuit.getCircuitId() + ". Element '"
								+ elemMap.get(circuit.getElemNumIn()).name + "' has invalid input contact number!!");
					}
				} else {
					throw new DModelException("Error in circuit #" + circuit.getCircuitId()
							+ ". Invalid elementIn ID!!");
				}
			}
		}

		LOG.debug("Ficitious contactsOut: " + fictElemContacts);
		// Check fictitious contacts' IDs
		for (int i = 1; i <= fictElemContacts.size(); i++) {
			if (!fictElemContacts.contains(i)) {
				throw new DModelException("Ficitious element has wrong contacts' IDs. Missed contact #" + i);
			}
		}

		LOG.info("Contacts IDs validation successfuly passed.");
	}

	/**
	 * Gets a set of element contacts.
	 * 
	 * @param contacts
	 *            String of element contacts.
	 * @return Set of element contacts.
	 */
	private Set<Integer> getElemContacts(String contacts) {
		Set<Integer> contactSet = new HashSet<Integer>();
		String[] contactsArray = contacts.split("\\s+");
		for (String cont : contactsArray) {
			contactSet.add(Integer.parseInt(cont));
		}
		return contactSet;
	}

	/**
	 * Checks the usage one element contact in several circuits.
	 * 
	 * @param circuitList
	 *            List of circuits.
	 * @throws DModelException
	 *             If there's one element contact used in several circuits or there's one output for several circuits.
	 */
	public void checkCircuit(List<CircuitStructure> circuitList) throws DModelException {
		int j = 1;
		for (CircuitStructure currentCircuit : circuitList) {
			for (int i = j; i < circuitList.size(); i++) {
				if (currentCircuit.getCircuitId() != circuitList.get(i).getCircuitId()) {
					if (currentCircuit.getElemNumOut() == circuitList.get(i).getElemNumOut()
							&& currentCircuit.getElemContactOut() == circuitList.get(i).getElemContactOut()
							|| currentCircuit.getElemNumIn() == circuitList.get(i).getElemNumIn()
							&& currentCircuit.getElemContactIn() == circuitList.get(i).getElemContactIn()) {
						throw new DModelException("Error in circuit #" + circuitList.get(i).getCircuitId()
								+ ". One contact in several circuits!!");
					}
				}
			}
			j++;
		}

		for (CircuitStructure currentCircuit : circuitList) {
			for (int i = 0; i < circuitList.size(); i++) {
				if (currentCircuit.getElemNumOut() == circuitList.get(i).getElemNumIn()
						&& currentCircuit.getElemContactOut() == circuitList.get(i).getElemContactIn()) {
					throw new DModelException("Error in circuits #" + currentCircuit.getCircuitId() + " and #"
							+ circuitList.get(i).getCircuitId()
							+ ". One output for several circuits!!");
				}
			}
		}
		LOG.info("Validation of contact usage in several circuits successfuly passed.");
	}
}
